/****************************************************************************
 *
 * General Object Type File
 * Copyright (c) 2007 Antrix Team
 *
 * This file may be distributed under the terms of the Q Public License
 * as defined by Trolltech ASA of Norway and appearing in the file
 * COPYING included in the packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#include "../DatabaseEnv.h"

#ifdef DATABASE_SUPPORT_PGSQL

PostgreDatabase::PostgreDatabase() : Database(DATABASE_TYPE_PGSQL)
{
	Connections = NULL;
	InUseMarkers = NULL;
	QueryBuffer = NULL;
	mConnectionCount = -1;   // Not connected.
	mNextPing = getMSTime();
	mQueryThread = NULL;
}

PostgreDatabase::~PostgreDatabase()
{
	// Close connections..

	for(int32 i = 0; i < mConnectionCount; ++i)
	{
		if(Connections)
			if(Connections[i])
				Disconnect(i);

		delete [] QueryBuffer[i];
	}

	delete [] Connections;
	delete [] InUseMarkers;
	delete [] QueryBuffer;
	delete [] DelayedQueryBuffer;
}

bool PostgreDatabase::Initialize(const char* Hostname, unsigned int port, const char* Username, const char* Password, const char* DatabaseName, uint32 ConnectionCount, uint32 BufferSize)
{
	mConnectionCount = ConnectionCount;

	// Build the connection string
	stringstream ss;
	ss << "host = " << Hostname << " port = " << port << " ";
	ss << "user = '" << Username << "' ";
	if(strlen(Password) > 0)
		ss << "password = '" << Password << "' ";

	ss << "dbname = '" << DatabaseName << "'";
	mConnectionString = ss.str();

	Connections = new PGconn*[mConnectionCount];
	InUseMarkers = new bool[mConnectionCount];
	QueryBuffer = new char*[mConnectionCount];
	DelayedQueryBuffer = new char[BufferSize];

	for(int i = 0; i < mConnectionCount; ++i)
	{
		Connections[i] = NULL;
		InUseMarkers[i] = false;
		QueryBuffer[i] = new char[BufferSize];
	}

	bool result = Connect();
	if(!result) return false;

	if(result && mConnectionCount > 1)
	{
		// Spawn MySQLDatabase thread
		//ZThread::Thread t(new PostgreDatabaseThread(this));
		//sLog.outString("sql: Spawned delayed MySQLDatabase query thread...");
	}
	return result;
}

bool PostgreDatabase::Connect()
{
	sLog.outString("Connecting to PostgreSQL Database with [%s]", mConnectionString.c_str());
	for(uint32 i = 0; i < mConnectionCount; ++i)
	{
		if(!Connect(i))
			return false;
	}
	//sLog.outString("sql: %u MySQL connections established.", mConnectionCount);
	return true;
}

bool PostgreDatabase::Connect(uint32 ConnectionIndex)
{
	Connections[ConnectionIndex] = PQconnectdb(mConnectionString.c_str());
	if(Connections[ConnectionIndex] == 0) return false;
	if(PQstatus(Connections[ConnectionIndex]) != CONNECTION_OK)
	{
		// failed for some reason
		sLog.outError("PostgreSQL connection failed: %s", PQerrorMessage(Connections[ConnectionIndex]));

		// free the memory
		Disconnect(ConnectionIndex);
		return false;
	}
	return true;
}

bool PostgreDatabase::Disconnect(uint32 ConnectionIndex)
{
	if(Connections[ConnectionIndex] == 0) return false;
	PQfinish(Connections[ConnectionIndex]);
	Connections[ConnectionIndex] = 0;
	return true;
}

uint32 PostgreDatabase::GetConnection()
{
	int32 index = -1;
	while(index == -1)
	{
		for(uint32 i = 0; i < mConnectionCount; ++i)
		{
			if(Connections[i] && InUseMarkers[i] == false)
			{
				index = i;
				break;
			}
		}
		ZThread::Thread::sleep(5);
	}
	return index;
}

void PostgreDatabase::Shutdown()
{
	sLog.outString("sql: Closing all PostgreSQL connections...");
	
	for(uint32 i = 0; i < mConnectionCount; ++i)
		Disconnect(i);
	
	sLog.outString("sql: %u connections closed.", mConnectionCount);
}

PGresult * PostgreDatabase::SendQuery(uint32 ConnectionIndex, const char* Sql, bool Self)
{
	PGresult * res = PQexec(Connections[ConnectionIndex], Sql);
	return res;
}

QueryResult * PostgreDatabase::Query(const char* QueryString, ...)
{
	if(QueryString == NULL) return NULL;

	va_list vlist;
	va_start(vlist, QueryString);

	mSearchMutex.acquire();
	// Find a free connection
	uint32 i = GetConnection();

	// Mark the connection as busy
	InUseMarkers[i] = true;
	mSearchMutex.release();

	// Apply parameters
	vsprintf(QueryBuffer[i], QueryString, vlist);
	va_end(vlist);

	// Send the query
	PGresult * res = SendQuery(i, QueryBuffer[i], false);
	InUseMarkers[i] = false;

	// Get the error code
	ExecStatusType result = PQresultStatus(res);
	if(result != PGRES_TUPLES_OK)
	{
		sLog.outError("Query failed: %s", PQresultErrorMessage(res));
		// command failed.
		PQclear(res);
		return 0;
	}

	// Better check the row count.. we don't want to return an empty query..
	if(PQntuples(res) == 0)
	{
		// oh noes!
		PQclear(res);
		return 0;
	}

	// get number of columns
	uint32 FieldCount = PQnfields(res);

	// get number of rows
	uint32 RowCount = PQntuples(res);

	// Create the QueryResult
	PostgreQueryResult * qResult = new PostgreQueryResult(res, FieldCount, RowCount);
	return qResult;
}

bool PostgreDatabase::Execute(const char* QueryString, ...)
{
	if(QueryString == NULL) return false;

	va_list vlist;
	va_start(vlist, QueryString);

	if(mQueryThread == 0)
	{
		DelayedQueryBufferMutex.acquire();

		vsprintf(DelayedQueryBuffer, QueryString, vlist);
		bool res = WaitExecute(DelayedQueryBuffer);

		DelayedQueryBufferMutex.release();
		return res;
	}

	/*DelayedQueryBufferMutex.acquire();

	vsprintf(DelayedQueryBuffer, QueryString, vlist);
	mQueryThread->AddQuery(DelayedQueryBuffer);

	DelayedQueryBufferMutex.release();*/
	return false;
}

bool PostgreDatabase::WaitExecute(const char* QueryString, ...)
{
	if(QueryString == NULL) return false;

	va_list vlist;
	va_start(vlist, QueryString);

	mSearchMutex.acquire();
	uint32 Connection = GetConnection();
	InUseMarkers[Connection] = true;
	mSearchMutex.release();

	vsprintf(QueryBuffer[Connection], QueryString, vlist);

	PGresult * res = SendQuery(Connection, QueryBuffer[Connection], false);
	if(res == 0) return false;
	InUseMarkers[Connection] = false;

	ExecStatusType result = PQresultStatus(res);
	bool passed = false;

	if(result == PGRES_TUPLES_OK || result == PGRES_COMMAND_OK)
		passed = true;
	else
		sLog.outError("Execute failed because of [%s]", PQresultErrorMessage(res));

	// free up the memory
	PQclear(res);

	return passed;
}

PostgreQueryResult::PostgreQueryResult(PGresult * res, uint32 FieldCount, uint32 RowCount) : QueryResult(FieldCount, RowCount, DATABASE_TYPE_PGSQL)
{
	// set result for later deletion and use
	mResult = res;

	// starting at row 0
	mRow = 0;

	// retreieve the data
	NextRow();
}

PostgreQueryResult::~PostgreQueryResult()
{

}

void PostgreQueryResult::Destroy()
{
	PQclear(mResult);
	mResult = 0;
}

bool PostgreQueryResult::NextRow()
{
	// check if we reached the end
	if(mRow == mRowCount) return false;

	// get each field and set it in result
	char * value;
	for(uint32 i = 0; i < mFieldCount; ++i)
	{
		value = PQgetvalue(mResult, mRow, i);
		if(value == 0) return false;

		mCurrentRow[i].SetValue(value);
	}

	mRow++;
	return true;
}

void PostgreDatabase::CheckConnections()
{
	// Check every 30 seconds (TODO: MAKE CUSTOMIZABLE)
	if(getMSTime() < mNextPing)
		return;

	mNextPing = getMSTime() + 60000;
	for(uint32 i = 0; i < mConnectionCount; ++i)
	{
		if(Connections[i] != 0 && PQstatus(Connections[i]) != CONNECTION_OK)
		{
			// disconnect and reconnect
			Disconnect(i);
			Connect(i);
		}
	}
}

#endif
